﻿using System;
using System.Collections.Generic;
using System.Text;
using EnvDTE;
using EnvDTE80;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Xml;
using System.IO;

namespace DocAddIn.Classes
{
    public static class Analysis
    {
        #region Constantes

        private const string CONST_ComentarioVazio = "<doc>\r\n</doc>";
        private const string CONST_ComentarioPadrao = "<doc>\r\n<summary>\r\n\r\n</summary>\r\n</doc>";
        private const string CONST_ComentarioMetodoAbreParam = "<param name=\"";
        private const string CONST_ComentarioMetodoFechaParam = "\"></param>\r\n";
        private const string CONST_ComentarioMetodoAbre = "<doc>\r\n<summary>\r\n</summary>\r\n<tables>\r\n</tables>\r\n";
        private const string CONST_ComentarioMetodoFecha = "<returns>\r\n</returns></doc>";

        private const string CONST_DataContractSemComentario = "O Data Contract não contém comentário";
        private const string CONST_DataMemberSemComentario = "O Data Member não contém comentário";
        private const string CONST_ServiceContractSemComentario = "O Service Contract não contém comentário";
        private const string CONST_OperationContractSemComentario = "O Operation Contract não contém comentário";

        private const string CONST_ServiceContractComentarioInvalido = "O comentário do Service Contract não está no padrão correto";
        private const string CONST_OperationContractComentarioInvalido = "O comentário do Operation Contract não está no padrão correto";
        private const string CONST_DataMemberComentarioInvalido = "O comentário do Data Member não está no padrão correto";
        private const string CONST_DataContractComentarioInvalido = "O comentário do Data Contract não está no padrão correto";

        private const string CONST_SummaryTag = "summary";
        private const string CONST_TableTag = "tables";
        private const string CONST_ReturnsTag = "returns";
        private const string CONST_ParamTag = "param";

        #endregion

        #region Campos

        private static string _tipoDataMemberFullName;
        private static string _tipoDataContractFullName;
        private static string _tipoServiceContractFullName;
        private static string _tipoOperationContractFullName;

        #endregion

        #region Construtor

        static Analysis()
        {
            _tipoDataMemberFullName = typeof(DataMemberAttribute).FullName;
            _tipoDataContractFullName = typeof(DataContractAttribute).FullName;
            _tipoServiceContractFullName = typeof(ServiceContractAttribute).FullName;
            _tipoOperationContractFullName = typeof(OperationContractAttribute).FullName;
        }

        #endregion

        #region Internal Methods

        internal static bool AnalisaItemAberto(Document documento)
        {
            VirtualPoint activePoint;
            CodeElement elemento = null;
            bool retorno = false;

            if (documento.ActiveWindow != null)
            {
                activePoint = ((TextSelection)documento.Selection).ActivePoint;
                elemento = activePoint.get_CodeElement(vsCMElement.vsCMElementClass);

                if (elemento != null)
                {
                    retorno = AnalisaDataContract((CodeClass)elemento);
                }
                else
                {
                    elemento = activePoint.get_CodeElement(vsCMElement.vsCMElementInterface);

                    if (elemento != null)
                    {
                        retorno = AnalisaServiceContract((CodeInterface)elemento);
                    }
                }
            }

            if (!retorno
                && elemento != null)
            {
                retorno = AnalisaParentElementoAberto(elemento);
            }

            return retorno;
        }

        internal static void ComentaItens(Document documento)
        {
            FileCodeModel fileCode;
            CodeElement codeElement;
            CodeNamespace codeNamespace;
            CodeElement filho;
            CodeElements fileCodeElements;
            CodeElements codeNamespaceElements;
            int countFileCodeElements;
            int countCodeNamespaceTotal;

            if (documento.ActiveWindow != null)
            {
                fileCode = (FileCodeModel)documento.ProjectItem.FileCodeModel;

                if (fileCode != null)
                {
                    //ganho de performance no loop
                    fileCodeElements = fileCode.CodeElements;
                    countFileCodeElements = fileCodeElements.Count;

                    for (int countCodeElements = 1; countCodeElements <= countFileCodeElements; countCodeElements++)
                    {
                        codeElement = fileCodeElements.Item(countCodeElements);

                        if (codeElement.Kind == vsCMElement.vsCMElementNamespace)
                        {
                            codeNamespace = (CodeNamespace)codeElement;

                            //ganho de performance no loop
                            codeNamespaceElements = codeNamespace.Children;
                            countCodeNamespaceTotal = codeNamespaceElements.Count;

                            for (int countCodeChildren = 1; countCodeChildren <= countCodeNamespaceTotal; countCodeChildren++)
                            {
                                filho = codeNamespaceElements.Item(countCodeChildren);

                                if (filho.Kind == vsCMElement.vsCMElementClass)
                                {
                                    ComentaClasseRecursivo((CodeClass)filho);
                                }
                                else if (filho.Kind == vsCMElement.vsCMElementInterface)
                                {
                                    ComentaInterfaceRecursivo((CodeInterface)filho);
                                }
                            }
                        }
                    }
                }
            }
        }

        internal static List<Problem> AnalisaBuidProjectItem(ProjectItem item)
        {
            FileCodeModel fileCode;
            CodeElement codeElement;
            CodeNamespace codeNamespace;
            CodeElement codeFilho;
            CodeElements fileCodeCodeElements;
            CodeElements codeNamespaceElements;
            ProjectItems itemProjectItems;
            int countFileCodeCodeElementsTotal;
            int countCodeNamespaceTotal;
            int countItemProjectItemsTotal;
            List<Problem> listaProblemas = new List<Problem>();

            fileCode = (FileCodeModel)item.FileCodeModel;

            if (fileCode != null)
            {
                //ganho de performance no loop
                fileCodeCodeElements = fileCode.CodeElements;
                countFileCodeCodeElementsTotal = fileCodeCodeElements.Count;

                if (fileCodeCodeElements != null)
                {
                    for (int countCodeElements = 1; countCodeElements <= countFileCodeCodeElementsTotal; countCodeElements++)
                    {
                        codeElement = fileCodeCodeElements.Item(countCodeElements);

                        if (codeElement.Kind == vsCMElement.vsCMElementNamespace
                            && codeElement.Children != null)
                        {
                            codeNamespace = (CodeNamespace)codeElement;

                            //ganho de performance no loop
                            codeNamespaceElements = codeNamespace.Children;
                            countCodeNamespaceTotal = codeNamespaceElements.Count;

                            for (int countCodeChildren = 1; countCodeChildren <= countCodeNamespaceTotal; countCodeChildren++)
                            {
                                codeFilho = codeNamespaceElements.Item(countCodeChildren);

                                if (codeFilho.Kind == vsCMElement.vsCMElementClass)
                                {
                                    listaProblemas.AddRange(AnalisaClasseBuild((CodeClass)codeFilho));
                                }
                                else if (codeFilho.Kind == vsCMElement.vsCMElementInterface)
                                {
                                    listaProblemas.AddRange(AnalisaInterfaceBuild((CodeInterface)codeFilho));
                                }
                            }
                        }
                    }
                }
            }

            //ganho de performance no loop
            itemProjectItems = item.ProjectItems;
            countItemProjectItemsTotal = itemProjectItems.Count;

            if (itemProjectItems != null)
            {
                for (int countProjectItems = 1; countProjectItems <= countItemProjectItemsTotal; countProjectItems++)
                {
                    listaProblemas.AddRange(AnalisaBuidProjectItem(itemProjectItems.Item(countProjectItems)));
                }
            }

            return listaProblemas;
        }

        #endregion

        #region Private Methods

        private static bool AnalisaParentElementoAberto(CodeElement elemento)
        {
            bool retorno = false;

            CodeElement pai = null;

            if (elemento.Kind == vsCMElement.vsCMElementClass)
            {
                pai = (CodeElement)((CodeClass)elemento).Parent;
            }
            else if (elemento.Kind == vsCMElement.vsCMElementInterface)
            {
                pai = (CodeElement)((CodeInterface)elemento).Parent;
            }

            if (pai != null)
            {
                if (pai.Kind == vsCMElement.vsCMElementClass)
                {
                    retorno = AnalisaDataContract((CodeClass)pai);
                }
                else if (pai.Kind == vsCMElement.vsCMElementInterface)
                {
                    retorno = AnalisaServiceContract((CodeInterface)pai);
                }

                if (!retorno)
                {
                    retorno = AnalisaParentElementoAberto(pai);
                }
            }

            return retorno;
        }

        private static void ComentaInterfaceRecursivo(CodeInterface inter)
        {
            CodeElement filho;
            CodeFunction metodo;
            CodeElements interChildren;
            CodeElements childrenParameters;
            int countInterChildren;
            int countChildrenParameters;
            StringBuilder parametros;

            if (AnalisaServiceContract(inter))
            {
                if (inter.DocComment == CONST_ComentarioVazio)
                {
                    inter.DocComment = CONST_ComentarioPadrao;
                }

                //ganho de performance no loop
                interChildren = inter.Children;
                countInterChildren = inter.Children.Count;

                for (int i = 1; i <= countInterChildren; i++)
                {
                    filho = interChildren.Item(i);

                    if (filho.Kind == vsCMElement.vsCMElementFunction)
                    {
                        metodo = (CodeFunction)filho;

                        if (AnalisaOperationContract(metodo)
                            && metodo.DocComment == CONST_ComentarioVazio)
                        {
                            parametros = new StringBuilder();

                            childrenParameters = metodo.Parameters;
                            countChildrenParameters = childrenParameters.Count;

                            for (int j = 1; j <= countChildrenParameters; j++)
                            {
                                parametros.Append(CONST_ComentarioMetodoAbreParam);
                                parametros.Append(childrenParameters.Item(j).Name);
                                parametros.Append(CONST_ComentarioMetodoFechaParam);
                            }

                            metodo.DocComment = String.Concat(CONST_ComentarioMetodoAbre,
                                                              parametros.ToString(),
                                                              CONST_ComentarioMetodoFecha);
                        }
                    }
                }
            }
        }

        private static void ComentaClasseRecursivo(CodeClass classe)
        {
            CodeElement filho;
            CodeProperty propriedade;
            CodeElements classeChildren;
            int countClasseChildren;

            if (AnalisaDataContract(classe))
            {
                if (classe.DocComment == CONST_ComentarioVazio)
                {
                    classe.DocComment = CONST_ComentarioPadrao;
                }

                //ganho de performance no loop
                classeChildren = classe.Children;
                countClasseChildren = classeChildren.Count;

                for (int i = 1; i <= countClasseChildren; i++)
                {
                    filho = classeChildren.Item(i);

                    if (filho.Kind == vsCMElement.vsCMElementProperty)
                    {
                        propriedade = (CodeProperty)filho;

                        if (AnalisaDataMember(propriedade)
                            && propriedade.DocComment == CONST_ComentarioVazio)
                        {
                            propriedade.DocComment = CONST_ComentarioPadrao;
                        }
                    }
                    else if (filho.Kind == vsCMElement.vsCMElementClass)
                    {
                        ComentaClasseRecursivo((CodeClass)filho);
                    }
                    else if (filho.Kind == vsCMElement.vsCMElementInterface)
                    {
                        ComentaInterfaceRecursivo((CodeInterface)filho);
                    }
                }
            }
        }

        private static List<Problem> AnalisaClasseBuild(CodeClass classe)
        {
            CodeElement filho;
            CodeElements children;
            CodeProperty propriedade;
            List<Problem> listaProblemas = new List<Problem>();
            string docComment;
            int countChildren;
            TextPoint startPoint;

            if (AnalisaDataContract(classe))
            {
                docComment = classe.DocComment;

                if (docComment == CONST_ComentarioVazio)
                {
                    //ganho de performance no add abaixo
                    startPoint = classe.StartPoint;

                    listaProblemas.Add(new Problem(CONST_DataContractSemComentario,
                                                   startPoint.Parent.Parent.FullName,
                                                   startPoint.Line - 1,
                                                   startPoint.DisplayColumn - 1));
                }
                else if (!AnalisaComentarioSimplesBuild(docComment))
                {
                    //ganho de performance no add abaixo
                    startPoint = classe.StartPoint;

                    listaProblemas.Add(new Problem(CONST_DataContractComentarioInvalido,
                                              startPoint.Parent.Parent.FullName,
                                              startPoint.Line - 1,
                                              startPoint.DisplayColumn - 1));
                }

                //ganho de performance no loop
                children = classe.Children;
                countChildren = children.Count;

                for (int i = 1; i <= countChildren; i++)
                {
                    filho = children.Item(i);

                    if (filho.Kind == vsCMElement.vsCMElementProperty)
                    {
                        propriedade = (CodeProperty)filho;
                        docComment = propriedade.DocComment;

                        if (AnalisaDataMember(propriedade))
                        {
                            if (docComment == CONST_ComentarioVazio)
                            {
                                //ganho de performance no add abaixo
                                startPoint = propriedade.StartPoint;

                                listaProblemas.Add(new Problem(CONST_DataMemberSemComentario,
                                                   startPoint.Parent.Parent.FullName,
                                                   startPoint.Line - 1,
                                                   startPoint.DisplayColumn - 1));
                            }
                            else if (!AnalisaComentarioSimplesBuild(docComment))
                            {
                                //ganho de performance no add abaixo
                                startPoint = propriedade.StartPoint;

                                listaProblemas.Add(new Problem(CONST_DataMemberComentarioInvalido,
                                                          startPoint.Parent.Parent.FullName,
                                                          startPoint.Line - 1,
                                                          startPoint.DisplayColumn - 1));
                            }
                        }
                    }
                    else if (filho.Kind == vsCMElement.vsCMElementInterface)
                    {
                        listaProblemas.AddRange(AnalisaInterfaceBuild((CodeInterface)filho));
                    }
                    else if (filho.Kind == vsCMElement.vsCMElementClass)
                    {
                        listaProblemas.AddRange(AnalisaClasseBuild((CodeClass)filho));
                    }
                }
            }

            return listaProblemas;
        }

        private static List<Problem> AnalisaInterfaceBuild(CodeInterface inter)
        {
            CodeElement filho;
            CodeFunction metodo;
            CodeElements children;
            CodeElements childrenParameters;
            List<Problem> listaProblemas = new List<Problem>();
            List<string> nomeParametros;
            string docComment;
            TextPoint startPoint;
            int countChildren;
            int countChildrenParameters;

            if (AnalisaServiceContract(inter))
            {
                docComment = inter.DocComment;

                if (docComment == CONST_ComentarioVazio)
                {
                    //ganho de performance no add abaixo
                    startPoint = inter.StartPoint;

                    listaProblemas.Add(new Problem(CONST_ServiceContractSemComentario,
                                                   startPoint.Parent.Parent.FullName,
                                                   startPoint.Line - 1,
                                                   startPoint.DisplayColumn - 1));
                }
                else if (!AnalisaComentarioSimplesBuild(docComment))
                {
                    //ganho de performance no add abaixo
                    startPoint = inter.StartPoint;

                    listaProblemas.Add(new Problem(CONST_ServiceContractComentarioInvalido,
                                              startPoint.Parent.Parent.FullName,
                                              startPoint.Line - 1,
                                              startPoint.DisplayColumn - 1));
                }

                //ganho de performance no loop
                children = inter.Children;
                countChildren = children.Count;

                for (int i = 1; i <= countChildren; i++)
                {
                    filho = children.Item(i);

                    if (filho.Kind == vsCMElement.vsCMElementFunction)
                    {
                        metodo = (CodeFunction)filho;
                        docComment = metodo.DocComment;

                        if (metodo.FunctionKind == vsCMFunction.vsCMFunctionFunction
                            && AnalisaOperationContract(metodo))
                        {
                            if (docComment == CONST_ComentarioVazio)
                            {
                                //ganho de performance no add abaixo
                                startPoint = metodo.StartPoint;

                                listaProblemas.Add(new Problem(CONST_OperationContractSemComentario,
                                                   startPoint.Parent.Parent.FullName,
                                                   startPoint.Line - 1,
                                                   startPoint.DisplayColumn - 1));
                            }
                            else
                            {
                                nomeParametros = new List<string>();

                                //ganho de performance no loop
                                childrenParameters = metodo.Parameters;
                                countChildrenParameters = childrenParameters.Count;

                                for (int j = 1; j <= countChildrenParameters; j++)
                                {
                                    nomeParametros.Add(childrenParameters.Item(j).Name);
                                }

                                if (!AnalisaComentarioComplexoBuild(docComment, nomeParametros))
                                {
                                    //ganho de performance no add abaixo
                                    startPoint = metodo.StartPoint;

                                    listaProblemas.Add(new Problem(CONST_OperationContractComentarioInvalido,
                                                              startPoint.Parent.Parent.FullName,
                                                              startPoint.Line - 1,
                                                              startPoint.DisplayColumn - 1));
                                }
                            }

                        }
                    }
                }
            }

            return listaProblemas;
        }

        private static bool AnalisaComentarioComplexoBuild(string docComment, List<string> nomeParametros)
        {            
            XmlNode nodeFilho;
            bool summary = false;
            bool tables = false;
            bool returns = false;
            string aux;
            string nodeFilhoName;
            List<string> parametrosEncontrados = new List<string>();
            XmlNodeList childNodes;
            int childNodesCount;
            XmlAttribute atributoNode;            

            //ganho de performance no loop
            childNodes = RetornaNodesComentario(docComment);

            if (childNodes != null)
            {
                childNodesCount = childNodes.Count;

                for (int i = 0; i < childNodesCount; i++)
                {
                    nodeFilho = childNodes[i];
                    nodeFilhoName = nodeFilho.Name;

                    if (!String.IsNullOrEmpty(nodeFilho.InnerText))
                    {
                        if (!summary
                            && nodeFilhoName == CONST_SummaryTag)
                        {
                            summary = true;
                        }
                        else if (!tables
                            && nodeFilhoName == CONST_TableTag)
                        {
                            tables = true;
                        }
                        else if (!returns
                            && nodeFilhoName == CONST_ReturnsTag)
                        {
                            returns = true;
                        }
                        else if (nodeFilhoName == CONST_ParamTag)
                        {
                            atributoNode = nodeFilho.Attributes["name"];

                            if (atributoNode != null)
                            {
                                aux = atributoNode.Value;

                                if (nomeParametros.Contains(aux)
                                    && !parametrosEncontrados.Contains(aux))
                                {
                                    parametrosEncontrados.Add(aux);
                                }
                            }
                        }
                    }
                }
            }

            return (summary
                    && tables
                    && returns
                    && (parametrosEncontrados.Count >= nomeParametros.Count));
        }

        private static bool AnalisaComentarioSimplesBuild(string docComment)
        {
            XmlNode nodeFilho;
            bool retorno = false;
            XmlNodeList childNodes;
            int childNodesCount;

            //ganho de performance no loop
            childNodes = childNodes = RetornaNodesComentario(docComment);

            if (childNodes != null)
            {
                childNodesCount = childNodes.Count;

                for (int i = 0; i < childNodesCount; i++)
                {
                    nodeFilho = childNodes[i];

                    if (nodeFilho.Name == CONST_SummaryTag
                        && !String.IsNullOrEmpty(nodeFilho.InnerText))
                    {
                        retorno = true;
                        break;
                    }
                }
            }

            return retorno;
        }

        private static XmlNodeList RetornaNodesComentario(string docComment)
        {
            List<string> linhasComentario;
            MemoryStream memory;
            XmlDocument xmlDoc;
            XmlNodeList retorno;

            try
            {
                linhasComentario = new List<string>(docComment.Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries));
                memory = new MemoryStream(System.Text.UTF8Encoding.UTF8.GetBytes(String.Concat(linhasComentario.ToArray())));
                xmlDoc = new XmlDocument();

                xmlDoc.Load(memory);
                memory.Close();
                memory.Dispose();

                retorno = xmlDoc.FirstChild.ChildNodes;
            }
            catch
            {
                retorno = null;
            }

            return retorno;
        }


        private static bool AnalisaOperationContract(CodeFunction metodo)
        {
            CodeElements atributos;
            int atributosCount;
            bool retorno = false;
            CodeAttribute atributoInter;

            //ganho de performance no loop
            atributos = metodo.Attributes;
            atributosCount = atributos.Count;

            for (int countInterAttributes = 1; countInterAttributes <= atributosCount; countInterAttributes++)
            {
                atributoInter = (CodeAttribute)atributos.Item(countInterAttributes);

                if (atributoInter.FullName == _tipoOperationContractFullName)
                {
                    retorno = true;
                    break;
                }
            }

            return retorno;
        }

        private static bool AnalisaDataMember(CodeProperty propriedade)
        {
            CodeElements atributos;
            int atributosCount;
            bool retorno = false;
            CodeAttribute atributoInter;

            //ganho de performance no loop
            atributos = propriedade.Attributes;
            atributosCount = atributos.Count;

            for (int countInterAttributes = 1; countInterAttributes <= atributosCount; countInterAttributes++)
            {
                atributoInter = (CodeAttribute)atributos.Item(countInterAttributes);

                if (atributoInter.FullName == _tipoDataMemberFullName)
                {
                    retorno = true;
                    break;
                }
            }

            return retorno;
        }

        private static bool AnalisaDataContract(CodeClass classe)
        {
            CodeElements atributos;
            int atributosCount;
            bool retorno = false;
            CodeAttribute atributoInter;

            //ganho de performance no loop
            atributos = classe.Attributes;
            atributosCount = atributos.Count;

            for (int countInterAttributes = 1; countInterAttributes <= atributosCount; countInterAttributes++)
            {
                atributoInter = (CodeAttribute)atributos.Item(countInterAttributes);

                if (atributoInter.FullName == _tipoDataContractFullName)
                {
                    retorno = true;
                    break;
                }
            }

            return retorno;
        }

        private static bool AnalisaServiceContract(CodeInterface inter)
        {
            CodeElements atributos;
            int atributosCount;
            bool retorno = false;
            CodeAttribute atributoInter;

            //ganho de performance no loop
            atributos = inter.Attributes;
            atributosCount = atributos.Count;

            for (int countInterAttributes = 1; countInterAttributes <= atributosCount; countInterAttributes++)
            {
                atributoInter = (CodeAttribute)atributos.Item(countInterAttributes);

                if (atributoInter.FullName == _tipoServiceContractFullName)
                {
                    retorno = true;
                    break;
                }
            }

            return retorno;
        }

        #endregion
    }
}
